package ru.tapublog.lib.samsungwallet.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.http.HTTPException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

import ru.tapublog.lib.samsungwallet.api.SamsungWalletException;
import ru.tapublog.lib.samsungwallet.api.SamsungWalletService;
import ru.tapublog.lib.samsungwallet.api.model.Ticket;
import ru.tapublog.lib.samsungwallet.impl.converter.TicketConverter;
import ru.tapublog.lib.samsungwallet.impl.dao.TextElementData;
import ru.tapublog.lib.samsungwallet.impl.dao.TicketDAO;

public class SamsungWallet implements SamsungWalletService {

	private static final String SERVICE_URL = "https://api.wallet.samsung.com:443/tkt/tickets";

	private String tmpAuth;
	private String associateId;
	private String associatePasscode;
	private URL serviceAdress;

	public static void main(String[] args) throws Exception {
		System.setProperty("org.apache.commons.logging.Log",
				"org.apache.commons.logging.impl.SimpleLog");

		System.setProperty("org.apache.commons.logging.simplelog.showdatetime",
				"true");

		System.setProperty(
				"org.apache.commons.logging.simplelog.log.httpclient.wire",
				"debug");

		System.setProperty(
				"org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient",
				"debug");

		SamsungWalletService service = new SamsungWallet(
				"f4ded63abeaa41cfa893dbc43963e07e",
				"326d0a14a5a5444b94ed2cda90373da3");

		Ticket ticket = new Ticket("2983928766167",
				"cc1a4435-3ff2-3e0d-81dd-7462de034ee7");

		// Text textElement = new Text("sa1", "Text", new byte[] { (byte) 255,
		// (byte) 255, (byte) 255 }, Align.MIDDLE);
		// Text textElement2 = new Text("sa2", "Text", new byte[] { (byte) 255,
		// (byte) 255, (byte) 255 }, Align.MIDDLE);
		// Barcode barcode = new Barcode("sa3", "caption", "12213",
		// BARCODE_TYPE.EAN_13);
		// Image image = new Image("sa4", "value", IMAGE_TYPE.PNG, Align.RIGHT);
		// SolidColorImage imageColor = new SolidColorImage("sa5", new byte[] {
		// (byte) 255, (byte) 255, (byte) 255 });
		// ticket.addViewElement(textElement);
		// ticket.addViewElement(textElement2);
		// ticket.addViewElement(barcode);
		// ticket.addViewElement(image);
		// ticket.addViewElement(imageColor);
		// // ticket.addAlert(new Alert("alert1",new GeofenceAlert("va", 1, 2,
		// 3,
		// // 4)));
		// // ticket.addAlert(new Alert("alert2",new GeofenceAlert("va", 1, 2,
		// 3,
		// // 4)));
		// ticket.addAlert(new DateAlert("dd", new Date(), 10));
		// ticket.addPartner(new Partner("parter1", new URL(
		// "https://qww.com/image1.png"), new URL("http://qww.com"),
		// new Date()));
		//
		// final String ticketId = service.issueTicket(ticket);
		// service.updateTicket(ticket);
		System.out.println(service.getTicketId(ticket));
	}

	public SamsungWallet(URL serviceUrl, String associate_id,
			String associate_passcode) throws MalformedURLException,
			SamsungWalletException {
		this.serviceAdress = serviceUrl;
		this.associateId = associate_id;
		this.associatePasscode = associate_passcode;
		updateAuth();

	}

	public SamsungWallet(String serviceUrl, String associate_id,
			String associate_passcode) throws MalformedURLException,
			SamsungWalletException {
		this(new URL(serviceUrl), associate_id, associate_passcode);
	}

	public SamsungWallet(String associate_id, String associate_passcode)
			throws MalformedURLException, SamsungWalletException {
		this(new URL(SERVICE_URL), associate_id, associate_passcode);
	}

	private void updateAuth() throws SamsungWalletException {
		if (associateId == null || associateId.isEmpty())
			throw new SamsungWalletException(
					"Assiciate id is null or empty - cannot create auth token");
		if (associatePasscode == null || associatePasscode.isEmpty())
			throw new SamsungWalletException(
					"Assiciate passcode is null or empty - cannot create auth token");
		tmpAuth = new String(Base64.encodeBase64(new String(String.format(
				"%s:%s", associateId, associatePasscode)).getBytes()));
	}

	public String issueTicket(Ticket ticket) throws SamsungWalletException {
		try {
			return sendIssueTicketRequest(convertToJson(ticket));
		} catch (SamsungWalletException ex) {
			throw ex;
		} catch (Exception ex) {
			throw new SamsungWalletException("Error issuing ticket", ex);
		}
	}

	private String convertToJson(Ticket ticket) throws JAXBException {
		TicketDAO dao = TicketConverter.convertTicket(ticket);
		Marshaller marshaller = createMarshaller();
		ByteArrayOutputStream oStream = new ByteArrayOutputStream();
		marshaller.marshal(dao, oStream);
		final String out = oStream.toString();
		return out;
	}

	public void updateTicket(Ticket ticket) throws SamsungWalletException {
		try {
			sendUpdateTicketRequest(ticket.getSerial(), convertToJson(ticket));
		} catch (SamsungWalletException ex) {
			throw ex;
		} catch (Exception ex) {
			throw new SamsungWalletException("Error sending update", ex);
		}
	}

	public String getTicketId(Ticket ticket) throws SamsungWalletException {
		return getTicketId(ticket.getSerial());
	}

	public String getTicketId(String ticketSerial)throws SamsungWalletException {
		try {
			return sendGetTicketRequest(ticketSerial);
		} catch (SamsungWalletException ex) {
			throw ex;
		} catch (Exception ex) {
			throw new SamsungWalletException("Error sending update", ex);
		}
	}

	private Marshaller createMarshaller() throws JAXBException {
		Map<String, Object> properties = new HashMap<String, Object>();
		properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
		properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
		JAXBContext jc = JAXBContext.newInstance(new Class[] { TicketDAO.class,
				TextElementData.class }, properties);

		Marshaller marshaller = jc.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		return marshaller;
	}

	private Unmarshaller createUnmarshaller() throws JAXBException {
		Map<String, Object> properties = new HashMap<String, Object>();
		properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
		properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
		JAXBContext jc = JAXBContext.newInstance(new Class[] { TicketDAO.class,
				TextElementData.class, Response.class, ErrorResponse.class },
				properties);

		Unmarshaller unmarshaller = jc.createUnmarshaller();
		return unmarshaller;
	}

	private String sendUpdateTicketRequest(String ticketSerial, String body)
			throws UnsupportedEncodingException, SamsungWalletException {
		PutMethod request = new PutMethod(serviceAdress.toString());
		request.setQueryString(new NameValuePair[] { new NameValuePair(
				"serial", ticketSerial) });
		request.setRequestEntity(new StringRequestEntity(body,
				"application/json", "UTF-8"));
		return sendRequest(request, HttpStatus.SC_ACCEPTED, false);
	}

	private String sendIssueTicketRequest(String body)
			throws UnsupportedEncodingException, SamsungWalletException {
		PostMethod request = new PostMethod(serviceAdress.toString());
		request.setRequestEntity(new StringRequestEntity(body,
				"application/json", "UTF-8"));
		return sendRequest(request, HttpStatus.SC_CREATED, true);
	}

	private String sendGetTicketRequest(String ticketSerial)
			throws UnsupportedEncodingException, SamsungWalletException {
		GetMethod request = new GetMethod(serviceAdress.toString());
		request.setQueryString(new NameValuePair[] {
				new NameValuePair("serial", ticketSerial),
				new NameValuePair("status", "downloaded"),
				new NameValuePair("show", "ticketId") });
		try {
			return sendRequest(request, HttpStatus.SC_OK, true);
		} catch (SamsungWalletException ex) {
			if (ex.getCode().equals("TKA1N2001"))
				return null;
			throw ex;
		}
	}

	private String sendRequest(HttpMethod method, final int okCode,
			final boolean expectResponse) throws SamsungWalletException {
		HttpClient client = new HttpClient();
		method.setRequestHeader("Authorization", "Basic " + new String(tmpAuth));
		method.setRequestHeader("X-TKT-Protocol-Version", "1.1");

		try {
			client.executeMethod(method);
			final Unmarshaller unmarshaller = createUnmarshaller();

			if (method.getStatusCode() == okCode) {
				if (!expectResponse)
					return null;
				Response response = unmarshaller.unmarshal(
						new StreamSource(method.getResponseBodyAsStream()),
						Response.class).getValue();
				return response.getTicketId();

			} else {
				ErrorResponse response = unmarshaller.unmarshal(
						new StreamSource(method.getResponseBodyAsStream()),
						ErrorResponse.class).getValue();
				throw new SamsungWalletException(response.getDescription(),
						response.getCode());
			}

		} catch (JAXBException ex) {
			throw new SamsungWalletException(
					"Error marshaling/unmarshaling messages", ex);
		} catch (IOException ex) {
			throw new SamsungWalletException("IO error", ex);
		} catch (HTTPException ex) {
			throw new SamsungWalletException("HTTP error", ex);
		} finally {
			method.releaseConnection();
		}
	}
}
